package com.fw.service.bouser.impl;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Locale;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;

import org.apache.log4j.Logger;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;

import com.fw.service.applicationlog.ApplicationLogService;
import com.fw.service.applicationlog.bean.ApplicationLogBean;
import com.fw.service.auditlog.AuditLogService;
import com.fw.service.auditlog.bean.AuditLogBean;
import com.fw.service.bean.EntryBean;
import com.fw.service.bean.ErrorBean;
import com.fw.service.bean.SearchCriteriaBean;
import com.fw.service.bean.SearchResultBean;
import com.fw.service.bouser.BOUserService;
import com.fw.service.bouser.bean.BOUserBean;
import com.fw.service.bouser.bean.SearchBOUserBean;
import com.fw.service.formvalidator.FormValidatorService;
import com.fw.service.passwordpolicy.PasswordPolicyService;
import com.fw.service.passwordpolicy.bean.PasswordPolicyBean;
import com.fw.service.persistence.BOUserPersistenceService;
import com.fw.service.persistence.dao.BOUserLoginDAO;
import com.fw.service.persistence.dao.BOUserPasswordHistoryDAO;
import com.fw.service.persistence.dto.BOUser;
import com.fw.service.persistence.dto.BOUserLogin;
import com.fw.service.persistence.dto.BOUserPasswordHistory;
import com.fw.service.persistence.dto.Error;
import com.fw.service.property.PropertyService;
import com.fw.util.DateUtils;
import com.fw.util.PageRequestUtils;
import com.fw.util.PasswordUtils;
import com.fw.util.StringUtils;

public class BOUserServiceImpl implements BOUserService {

    private static Logger log = Logger.getLogger(BOUserServiceImpl.class.getName());

    @Autowired
    private PropertyService propertyService;

    @Autowired
    private ApplicationLogService applicationLogService;

    @Autowired
    private AuditLogService auditLogService;

    @Autowired
    private FormValidatorService formValidatorService;

    @Autowired
    private BOUserPersistenceService boUserPersistenceService;

    @Autowired
    private PasswordPolicyService passwordPolicyService;

    @Autowired
    private BOUserLoginDAO boUserLoginDAO;

    @Autowired
    private BOUserPasswordHistoryDAO boUserPasswordHistoryDAO;

    @Override
    public List<EntryBean<String, Serializable>> createBOUser(BOUserBean boUserBean) {
	String clazz = BOUserService.class.getName();
	String method = "createBOUser";
	List<EntryBean<String, Serializable>> entryBeans = null;
	try {
	    ApplicationLogBean applicationLogBean = new ApplicationLogBean();
	    applicationLogBean.setType(ApplicationLogBean.TYPE_INFO);
	    applicationLogBean.setClazz(clazz);
	    applicationLogBean.setMethod(method);
	    applicationLogBean.setMessage(method);
	    applicationLogService.createApplicationLog(applicationLogBean);

	    if (boUserBean != null) {
		entryBeans = new ArrayList<EntryBean<String, Serializable>>();
		// validate
		List<Error> errors = formValidatorService.validate(clazz, method, boUserBean);
		List<ErrorBean> errorBeans = new ArrayList<ErrorBean>();
		if (errors != null && !errors.isEmpty()) {
		    for (Error error : errors) {
			ErrorBean errorBean = new ErrorBean();
			BeanUtils.copyProperties(error, errorBean);
			errorBeans.add(errorBean);
		    }
		}
		// validate unique
		if (!this.checkUniqueUsername(boUserBean.getUsername())) {
		    ErrorBean errorBean = new ErrorBean();

		    errorBean.setErrorCode("errors.com.fw.service.bouser.BOUserService.createBOUser.usernameExist");
		    errorBeans.add(errorBean);
		}
		if (errorBeans != null && !errorBeans.isEmpty()) {
		    entryBeans.add(new EntryBean<String, Serializable>(List.class.getName() + "." + ErrorBean.class.getName(),
			    (Serializable) errorBeans));
		} else {
		    Date currentDate = DateUtils.getCurrentDate();
		    String currentDateStr = StringUtils.formatDate(currentDate, StringUtils.PATTERN_DATETIME_PERSISTENCE);
		    // save
		    BOUser boUser = new BOUser();
		    BeanUtils.copyProperties(boUserBean, boUser);
		    boUser.setStatus(BOUser.STATUS_ACTIVE);
		    boUser.setUserType(BOUser.USER_TYPE_AEON_USER);
		    Calendar calendar = Calendar.getInstance(Locale.ENGLISH);
		    // load password policy
		    PasswordPolicyBean passwordPolicyBean = passwordPolicyService.getPasswordPolicy();
		    Integer passwordExpiryPeriodDay = 15;
		    if (passwordPolicyBean != null && passwordPolicyBean.getPasswordExpiryPeriodDay() != null) {
			passwordExpiryPeriodDay = passwordPolicyBean.getPasswordExpiryPeriodDay();
		    }
		    calendar.add(Calendar.DAY_OF_YEAR, passwordExpiryPeriodDay);
		    calendar.set(Calendar.HOUR_OF_DAY, 0);
		    calendar.set(Calendar.MINUTE, 0);
		    calendar.set(Calendar.SECOND, 0);
		    calendar.set(Calendar.MILLISECOND, 0);
		    boUser.setExpiredDate(StringUtils.formatDate(calendar.getTime(), StringUtils.PATTERN_DATE_PERSISTENCE));
		    boUser.setCreateBy(boUserBean.getLoginUsername());
		    boUser.setCreateDatetime(currentDateStr);
		    boUser.setLastUpdateBy(boUserBean.getLoginUsername());
		    boUser.setLastUpdateDatetime(currentDateStr);
		    // auto create password
		    String password = passwordPolicyService.generatePassword(Boolean.TRUE);
		    String passwordHash = PasswordUtils.generateSecuredPasswordHash(password);
		    this.createBOUserPasswordHistory(boUser.getId(), passwordHash, currentDateStr);

		    boUser.setPasswordHash(passwordHash);
		    boUser.setPasswordStatus(BOUser.PASSWORD_STATUS_FORCE_CHANGE);
		    boUser = boUserPersistenceService.save(boUser);

		    BOUserBean resultBoUserBean = new BOUserBean();
		    BeanUtils.copyProperties(boUser, resultBoUserBean);
		    resultBoUserBean.setId(boUser.getId());
		    resultBoUserBean.setPassword(password);
		    // audit log
		    String logActionType = propertyService.getProperty("auditlog." + clazz + "." + method);
		    if (logActionType != null) {
			AuditLogBean auditLogBean = new AuditLogBean();
			auditLogBean.setLogActionType(logActionType);
			auditLogBean.setDescription(boUserBean.getUsername() + " created by " + boUserBean.getLoginUsername());
			auditLogBean.setGroupId(boUserBean.getLoginRoleId());
			auditLogBean.setUserId(boUserBean.getLoginUserId());
			auditLogBean.setDateTime(currentDateStr);
			auditLogBean.setUserIp(boUserBean.getUserIp());
			auditLogService.createAuditLog(auditLogBean);
		    }
		    entryBeans.add(new EntryBean<String, Serializable>(resultBoUserBean.getClass().getName(), resultBoUserBean));
		}
	    }
	} catch (Exception e) {
	    ApplicationLogBean applicationLogBean = new ApplicationLogBean();
	    applicationLogBean.setType(ApplicationLogBean.TYPE_ERROR);
	    applicationLogBean.setClazz(clazz);
	    applicationLogBean.setMethod(method);
	    applicationLogBean.setMessage(e.getMessage());
	    applicationLogService.createApplicationLog(applicationLogBean);
	}
	return entryBeans;
    }

    @Override
    public BOUserBean getBOUser(Long id) {
	BOUserBean boUserBean = null;
	try {
	    BOUser boUser = boUserPersistenceService.findOne(id);
	    if (boUser != null && !BOUser.STATUS_CLOSED.equals(boUser.getStatus())) {
		boUserBean = new BOUserBean();
		BeanUtils.copyProperties(boUser, boUserBean);
		boUserBean.setId(boUser.getId());
	    }
	} catch (Exception e) {
	    log.error(e.getMessage(), e);
	}
	return boUserBean;
    }

    @Override
    public List<EntryBean<String, Serializable>> updateBOUser(BOUserBean boUserBean) {
	String clazz = BOUserService.class.getName();
	String method = "updateBOUser";
	List<EntryBean<String, Serializable>> entryBeans = null;
	BOUser boUser = null;
	try {

	    if (boUserBean != null) {
		boUser = boUserPersistenceService.findOne(boUserBean.getId());
		if (boUser != null && isDataChange(boUser, boUserBean)) {

		    ApplicationLogBean applicationLogBean = new ApplicationLogBean();
		    applicationLogBean.setType(ApplicationLogBean.TYPE_INFO);
		    applicationLogBean.setClazz(clazz);
		    applicationLogBean.setMethod(method);
		    applicationLogBean.setMessage(method);
		    applicationLogService.createApplicationLog(applicationLogBean);

		    entryBeans = new ArrayList<EntryBean<String, Serializable>>();
		    // validate
		    List<Error> errors = formValidatorService.validate(clazz, method, boUserBean);
		    List<ErrorBean> errorBeans = new ArrayList<ErrorBean>();
		    if (errors != null && !errors.isEmpty()) {
			for (Error error : errors) {
			    ErrorBean errorBean = new ErrorBean();
			    BeanUtils.copyProperties(error, errorBean);
			    errorBeans.add(errorBean);
			}
		    }
		    if (errorBeans != null && !errorBeans.isEmpty()) {
			entryBeans.add(new EntryBean<String, Serializable>(List.class.getName() + "." + ErrorBean.class.getName(),
				(Serializable) errorBeans));
		    } else {
			// if (boUser != null && !BOUser.STATUS_INACTIVE.equals(boUser.getStatus())) {
			Date currentDate = DateUtils.getCurrentDate();
			String currentDateStr = StringUtils.formatDate(currentDate, StringUtils.PATTERN_DATETIME_PERSISTENCE);
			// copy from bean
			boUser.setEmail(boUserBean.getEmail());
			boUser.setEmployeeID(boUserBean.getEmployeeID());
			boUser.setEmployeeName(boUserBean.getEmployeeName());
			boUser.setRole(boUserBean.getRole());

			String password = null;
			if ((BOUser.STATUS_CLOSED.equals(boUser.getStatus()) || BOUser.STATUS_EXPIRED.equals(boUser.getStatus())
				|| BOUser.STATUS_LOCKED.equals(boUser.getStatus()) || BOUser.STATUS_INACTIVE.equals(boUser.getStatus()))
				&& BOUser.STATUS_ACTIVE.equals(boUserBean.getStatus())) {
			    // auto create new password
			    password = passwordPolicyService.generatePassword(Boolean.TRUE);
			    String passwordHash = PasswordUtils.generateSecuredPasswordHash(password);
			    this.createBOUserPasswordHistory(boUser.getId(), passwordHash, currentDateStr);

			    boUser.setPasswordHash(passwordHash);
			    boUser.setPasswordStatus(BOUser.PASSWORD_STATUS_FORCE_CHANGE);

			    // reset login attempt count
			    BOUserLogin boUserLogin = boUserLoginDAO.findByUserId(boUser.getId());
			    if (boUserLogin != null) {
				boUserLogin.setLoginAttemptCount(0);
				boUserLoginDAO.save(boUserLogin);
			    }
			}
			boUser.setStatus(boUserBean.getStatus());

			boUser.setLastUpdateBy(boUserBean.getLoginUsername());
			boUser.setLastUpdateDatetime(currentDateStr);
			boUser = boUserPersistenceService.save(boUser);
			BOUserBean resultBoUserBean = new BOUserBean();
			BeanUtils.copyProperties(boUser, resultBoUserBean);
			resultBoUserBean.setId(boUser.getId());
			resultBoUserBean.setPassword(password);

			// audit log
			String logActionType = propertyService.getProperty("auditlog." + clazz + "." + method);
			if (logActionType != null) {
			    AuditLogBean auditLogBean = new AuditLogBean();
			    auditLogBean.setLogActionType(logActionType);
			    auditLogBean.setDescription(boUserBean.getLoginUsername() + " edited " + boUserBean.getUsername() + " profile");
			    auditLogBean.setGroupId(boUserBean.getLoginRoleId());
			    auditLogBean.setUserId(boUserBean.getLoginUserId());
			    auditLogBean.setDateTime(currentDateStr);
			    auditLogBean.setUserIp(boUserBean.getUserIp());
			    auditLogService.createAuditLog(auditLogBean);
			}
			entryBeans.add(new EntryBean<String, Serializable>(resultBoUserBean.getClass().getName(), resultBoUserBean));
			// }
		    }
		} else {
		    entryBeans = new ArrayList<EntryBean<String, Serializable>>();
		    entryBeans.add(new EntryBean<String, Serializable>(boUserBean.getClass().getName(), boUserBean));
		}
	    }
	} catch (Exception e) {
	    ApplicationLogBean applicationLogBean = new ApplicationLogBean();
	    applicationLogBean.setType(ApplicationLogBean.TYPE_ERROR);
	    applicationLogBean.setClazz(clazz);
	    applicationLogBean.setMethod(method);
	    applicationLogBean.setMessage(e.getMessage());
	    applicationLogService.createApplicationLog(applicationLogBean);
	}
	return entryBeans;
    }

    @Override
    public List<EntryBean<String, Serializable>> deleteBOUser(BOUserBean boUserBean) {
	String clazz = BOUserService.class.getName();
	String method = "deleteBOUser";
	List<EntryBean<String, Serializable>> entryBeans = null;
	BOUser boUser = null;
	try {
	    if (boUserBean != null) {
		Date currentDate = DateUtils.getCurrentDate();
		String currentDateStr = StringUtils.formatDate(currentDate, StringUtils.PATTERN_DATETIME_PERSISTENCE);
		entryBeans = new ArrayList<EntryBean<String, Serializable>>();

		// validate
		List<Error> errors = formValidatorService.validate(clazz, method, boUserBean);
		List<ErrorBean> errorBeans = new ArrayList<ErrorBean>();
		if (errors != null && !errors.isEmpty()) {
		    for (Error error : errors) {
			ErrorBean errorBean = new ErrorBean();
			BeanUtils.copyProperties(error, errorBean);
			errorBeans.add(errorBean);
		    }
		} else {
		    Long boUserId = boUserBean.getId();
		    boUser = boUserPersistenceService.findOne(boUserId);
		    if (boUser == null) {
			errorBeans.add(new ErrorBean("errors.com.fw.service.bouser.BOUserService.deleteBOUser.notfoundBOUser"));
		    }
		}
		// result
		if (errorBeans != null && !errorBeans.isEmpty()) {
		    entryBeans.add(new EntryBean<String, Serializable>(List.class.getName() + "." + ErrorBean.class.getName(),
			    (Serializable) errorBeans));
		} else {

		    // update status
		    boUser.setStatus(BOUser.STATUS_INACTIVE);
		    boUser.setLastUpdateBy(boUserBean.getLoginUsername());
		    boUser.setLastUpdateDatetime(currentDateStr);
		    boUser = boUserPersistenceService.save(boUser);

		    BOUserBean resultBoUserBean = new BOUserBean();
		    BeanUtils.copyProperties(boUser, resultBoUserBean);
		    resultBoUserBean.setId(boUser.getId());

		    // audit log
		    String logActionType = propertyService.getProperty("auditlog." + clazz + "." + method);
		    if (logActionType != null) {
			AuditLogBean auditLogBean = new AuditLogBean();
			auditLogBean.setLogActionType(logActionType);
			auditLogBean.setDescription("BO User, Username: " + boUser.getUsername() + " deleted by "
				+ boUserBean.getLoginUsername());
			auditLogBean.setGroupId(boUserBean.getLoginRoleId());
			auditLogBean.setUserId(boUserBean.getLoginUserId());
			auditLogBean.setDateTime(currentDateStr);
			auditLogBean.setUserIp(boUserBean.getUserIp());
			auditLogService.createAuditLog(auditLogBean);
		    }
		    entryBeans.add(new EntryBean<String, Serializable>(resultBoUserBean.getClass().getName(), resultBoUserBean));
		}
	    }
	} catch (Exception e) {
	    ApplicationLogBean applicationLogBean = new ApplicationLogBean();
	    applicationLogBean.setType(ApplicationLogBean.TYPE_ERROR);
	    applicationLogBean.setClazz(clazz);
	    applicationLogBean.setMethod(method);
	    applicationLogBean.setMessage(e.getMessage());
	    applicationLogService.createApplicationLog(applicationLogBean);
	}
	return entryBeans;
    }

    @Override
    public Boolean checkUniqueUsername(String username) {
	Boolean result = Boolean.FALSE;
	try {
	    List<BOUser> boUsers = boUserPersistenceService.selectByUsernameAndStatus(username, null);
	    if (boUsers == null || boUsers.isEmpty()) {
		result = Boolean.TRUE;
	    }
	} catch (Exception e) {
	    log.error(e.getMessage(), e);
	}
	return result;
    }

    @Override
    public List<BOUserBean> selectAllBOUser() {
	List<BOUserBean> boUserBeans = null;
	try {
	    List<BOUser> boUsers = boUserPersistenceService.selectByStatus(BOUser.STATUS_ACTIVE);
	    if (boUsers != null && !boUsers.isEmpty()) {
		boUserBeans = new ArrayList<BOUserBean>();
		for (BOUser boUser : boUsers) {
		    BOUserBean boUserBean = new BOUserBean();
		    BeanUtils.copyProperties(boUser, boUserBean);
		    boUserBean.setId(boUser.getId());
		    boUserBeans.add(boUserBean);
		}
	    }
	} catch (Exception e) {
	    log.error(e.getMessage(), e);
	}
	return boUserBeans;
    }

    @Override
    public SearchResultBean<BOUserBean> selectBOUser(SearchCriteriaBean<SearchBOUserBean> searchCriteriaBean) {
	SearchResultBean<BOUserBean> searchResultBean = null;
	try {
	    if (searchCriteriaBean != null) {
		SearchBOUserBean searchBOUserBean = searchCriteriaBean.getCriteriaBean();
		if (searchBOUserBean != null) {
		    final String username = searchBOUserBean.getUsername();
		    final String email = searchBOUserBean.getEmail();
		    final String employeeID = searchBOUserBean.getEmployeeID();
		    final String employeeName = searchBOUserBean.getEmployeeName();
		    final String role = searchBOUserBean.getRole();
		    final String status = searchBOUserBean.getStatus();
		    final String createBy = searchBOUserBean.getCreateBy();
		    final String lastUpdateBy = searchBOUserBean.getLastUpdateBy();
		    final Date expiredDateFrom = searchBOUserBean.getExpiredDateFrom();
		    final Date expiredDateTo = searchBOUserBean.getExpiredDateTo();
		    final Date createDateFrom = searchBOUserBean.getCreateDateFrom();
		    final Date createDateTo = searchBOUserBean.getCreateDateTo();
		    final Date lastUpdateDateFrom = searchBOUserBean.getLastUpdateDateFrom();
		    final Date lastUpdateDateTo = searchBOUserBean.getLastUpdateDateTo();

		    Specification<BOUser> specification = new Specification<BOUser>() {
			@Override
			public Predicate toPredicate(Root<BOUser> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
			    List<Predicate> predicates = new ArrayList<Predicate>();
			    if (username != null && !username.isEmpty()) {
				predicates.add(cb.like(root.<String> get("username"), "%" + username + "%"));
			    }
			    if (email != null && !email.isEmpty()) {
				predicates.add(cb.like(root.<String> get("email"), "%" + email + "%"));
			    }
			    if (employeeID != null && !employeeID.isEmpty()) {
				predicates.add(cb.like(root.<String> get("employeeID"), "%" + employeeID + "%"));
			    }
			    if (employeeName != null && !employeeName.isEmpty()) {
				predicates.add(cb.like(root.<String> get("employeeName"), "%" + employeeName + "%"));
			    }
			    if (role != null && !role.isEmpty()) {
				predicates.add(cb.equal(root.<String> get("role"), role));
			    }
			    if (status != null && !status.isEmpty()) {
				predicates.add(cb.equal(root.<String> get("status"), status));
			    } else {
				predicates.add(cb.notEqual(root.<String> get("status"), BOUser.STATUS_CLOSED));
			    }
			    if (createBy != null && !createBy.isEmpty()) {
				predicates.add(cb.like(root.<String> get("createBy"), "%" + createBy + "%"));
			    }
			    if (lastUpdateBy != null && !lastUpdateBy.isEmpty()) {
				predicates.add(cb.like(root.<String> get("lastUpdateBy"), "%" + lastUpdateBy + "%"));
			    }
			    if (expiredDateFrom != null) {
				predicates.add(cb.greaterThanOrEqualTo(root.<String> get("expiredDate"),
					StringUtils.formatDate(expiredDateFrom, StringUtils.PATTERN_DATE_PERSISTENCE)));
			    }
			    if (expiredDateTo != null) {
				predicates.add(cb.lessThanOrEqualTo(root.<String> get("expiredDate"),
					StringUtils.formatDate(expiredDateTo, StringUtils.PATTERN_DATE_PERSISTENCE)));
			    }
			    if (createDateFrom != null) {
				predicates.add(cb.greaterThanOrEqualTo(root.<String> get("createDatetime"), StringUtils.formatDate(
					DateUtils.getFromDate(createDateFrom), StringUtils.PATTERN_DATETIME_PERSISTENCE)));
			    }
			    if (createDateTo != null) {
				predicates
					.add(cb.lessThanOrEqualTo(root.<String> get("createDatetime"), StringUtils.formatDate(
						DateUtils.getToDate(createDateTo), StringUtils.PATTERN_DATETIME_PERSISTENCE)));
			    }
			    if (lastUpdateDateFrom != null) {
				predicates.add(cb.greaterThanOrEqualTo(root.<String> get("lastUpdateDatetime"), StringUtils.formatDate(
					DateUtils.getFromDate(lastUpdateDateFrom), StringUtils.PATTERN_DATETIME_PERSISTENCE)));
			    }
			    if (lastUpdateDateTo != null) {
				predicates.add(cb.lessThanOrEqualTo(root.<String> get("lastUpdateDatetime"), StringUtils.formatDate(
					DateUtils.getToDate(lastUpdateDateTo), StringUtils.PATTERN_DATETIME_PERSISTENCE)));
			    }
			    Predicate result = cb.conjunction();
			    for (Predicate predicate : predicates) {
				result.getExpressions().add(predicate);
			    }
			    return result;
			}
		    };
		    Page<BOUser> page = boUserPersistenceService
			    .findAll(specification, PageRequestUtils.getPageRequest(searchCriteriaBean));
		    if (page != null) {
			searchResultBean = new SearchResultBean<BOUserBean>();
			searchResultBean.setTotalElements(page.getTotalElements());
			searchResultBean.setTotalPages(page.getTotalPages());
			List<BOUserBean> content = new ArrayList<BOUserBean>();
			List<BOUser> boUsers = page.getContent();
			if (boUsers != null && !boUsers.isEmpty()) {
			    for (BOUser boUser : boUsers) {
				BOUserBean bean = new BOUserBean();
				BeanUtils.copyProperties(boUser, bean);
				bean.setId(boUser.getId());
				content.add(bean);
			    }
			}
			searchResultBean.setContent(content);
		    }
		}
	    }
	} catch (Exception e) {
	    log.error(e.getMessage(), e);
	}
	return searchResultBean;
    }

    @Override
    public List<EntryBean<String, Serializable>> resetPasswordBOUser(BOUserBean boUserBean) {
	String clazz = BOUserService.class.getName();
	String method = "resetPasswordBOUser";
	List<EntryBean<String, Serializable>> entryBeans = null;
	try {
	    if (boUserBean != null && boUserBean.getId() != null) {
		entryBeans = new ArrayList<EntryBean<String, Serializable>>();
		List<ErrorBean> errorBeans = new ArrayList<ErrorBean>();
		BOUser boUser = boUserPersistenceService.findOne(boUserBean.getId());
		Date currentDate = DateUtils.getCurrentDate();
		String currentDateStr = StringUtils.formatDate(currentDate, StringUtils.PATTERN_DATETIME_PERSISTENCE);
		String password = passwordPolicyService.generatePassword(Boolean.TRUE);
		String passwordHash = PasswordUtils.generateSecuredPasswordHash(password);
		this.createBOUserPasswordHistory(boUser.getId(), passwordHash, currentDateStr);

		boUser.setPasswordHash(passwordHash);
		boUser.setPasswordStatus(BOUser.PASSWORD_STATUS_FORCE_CHANGE);
		boUser.setLastUpdateBy(boUserBean.getLoginUsername());
		boUser.setLastUpdateDatetime(currentDateStr);
		boUser = boUserPersistenceService.save(boUser);

		BOUserBean resultBoUserBean = new BOUserBean();
		BeanUtils.copyProperties(boUser, resultBoUserBean);
		resultBoUserBean.setId(boUser.getId());
		resultBoUserBean.setPassword(password);
		// audit log
		String logActionType = propertyService.getProperty("auditlog." + clazz + "." + method);
		if (logActionType != null) {
		    AuditLogBean auditLogBean = new AuditLogBean();
		    auditLogBean.setLogActionType(logActionType);
		    auditLogBean.setDescription(boUserBean.getLoginUsername() + " reset password " + boUserBean.getUsername());
		    auditLogBean.setGroupId(boUserBean.getLoginRoleId());
		    auditLogBean.setUserId(boUserBean.getLoginUserId());
		    auditLogBean.setDateTime(currentDateStr);
		    auditLogBean.setUserIp(boUserBean.getUserIp());
		    auditLogService.createAuditLog(auditLogBean);
		}
		if (errorBeans != null && !errorBeans.isEmpty()) {
		    entryBeans.add(new EntryBean<String, Serializable>(List.class.getName() + "." + ErrorBean.class.getName(),
			    (Serializable) errorBeans));
		} else {
		    entryBeans.add(new EntryBean<String, Serializable>(resultBoUserBean.getClass().getName(), resultBoUserBean));
		}
	    }
	} catch (Exception e) {
	    ApplicationLogBean applicationLogBean = new ApplicationLogBean();
	    applicationLogBean.setType(ApplicationLogBean.TYPE_ERROR);
	    applicationLogBean.setClazz(clazz);
	    applicationLogBean.setMethod(method);
	    applicationLogBean.setMessage(e.getMessage());
	    applicationLogService.createApplicationLog(applicationLogBean);
	}
	return entryBeans;
    }

    private boolean isDataChange(BOUser boUser, BOUserBean boUserBean) {
	boolean isChange = false;
	if (boUser != null && boUserBean != null) {
	    if (!boUser.getEmail().equals(boUserBean.getEmail()) || !boUser.getEmployeeID().equals(boUserBean.getEmployeeID())
		    || !boUser.getEmployeeName().equals(boUserBean.getEmployeeName()) || !boUser.getRole().equals(boUserBean.getRole())
		    || !boUser.getStatus().equals(boUserBean.getStatus())) {
		isChange = true;
	    }
	}
	return isChange;
    }

    @Override
    public List<EntryBean<String, Serializable>> changePasswordBOUser(BOUserBean boUserBean) {
	String clazz = BOUserService.class.getName();
	String method = "changePasswordBOUser";
	List<EntryBean<String, Serializable>> entryBeans = null;
	BOUser boUser = null;
	String newPassword = null;
	try {
	    if (boUserBean != null) {
		Date currentDate = DateUtils.getCurrentDate();
		String currentDateStr = StringUtils.formatDate(currentDate, StringUtils.PATTERN_DATETIME_PERSISTENCE);
		entryBeans = new ArrayList<EntryBean<String, Serializable>>();

		// validate
		List<Error> errors = formValidatorService.validate(clazz, method, boUserBean);
		List<ErrorBean> errorBeans = new ArrayList<ErrorBean>();
		if (errors != null && !errors.isEmpty()) {
		    for (Error error : errors) {
			ErrorBean errorBean = new ErrorBean();
			BeanUtils.copyProperties(error, errorBean);
			errorBeans.add(errorBean);
		    }
		} else {
		    Long boUserId = boUserBean.getId();
		    String oldPassword = boUserBean.getOldPassword();
		    newPassword = boUserBean.getNewPassword();

		    // check password policy
		    ErrorBean errorBean = passwordPolicyService.validateInputPassword(newPassword);
		    if (errorBean != null) {
			errorBeans.add(errorBean);
		    } else {
			List<ErrorBean> policyErrorBeans = passwordPolicyService.validatePasswordPolicy(newPassword);
			if (policyErrorBeans != null && !policyErrorBeans.isEmpty()) {
			    errorBeans.addAll(policyErrorBeans);
			} else {
			    boUser = boUserPersistenceService.findOne(boUserId);
			    if (boUser == null) {
				errorBeans.add(new ErrorBean(
					"errors.com.fw.service.bouser.BOUserService.changePasswordBOUser.notfoundBOUser"));
			    }
			    // check old password match ?
			    if (!PasswordUtils.validatePassword(oldPassword, boUser.getPasswordHash())) {
				errorBeans.add(new ErrorBean(
					"errors.com.fw.service.bouser.BOUserService.changePasswordBOUser.oldPasswordNotMatch"));
			    } else {
				Integer getEnforePasswordHistory = passwordPolicyService.getPasswordPolicy().getEnforePasswordHistory();
				if (getEnforePasswordHistory != null) {
				    Pageable pageable = new PageRequest(0, getEnforePasswordHistory);
				    List<BOUserPasswordHistory> passwordHistories = boUserPasswordHistoryDAO
					    .findByUserIdOrderByLastPasswordDateTimeDesc(boUserId, pageable);
				    // check duplicate in password history
				    if (passwordHistories != null && !passwordHistories.isEmpty()) {
					for (BOUserPasswordHistory boUserPasswordHistory : passwordHistories) {
					    if (PasswordUtils.validatePassword(newPassword, boUserPasswordHistory.getLastPasswordHash())) {
						errorBeans.add(new ErrorBean(
							"errors.com.fw.service.bouser.BOUserService.changePasswordBOUser.newPasswordUsed"));
						break;
					    }
					}
				    }
				}
			    }
			}
		    }
		}

		// result
		if (errorBeans != null && !errorBeans.isEmpty()) {
		    entryBeans.add(new EntryBean<String, Serializable>(List.class.getName() + "." + ErrorBean.class.getName(),
			    (Serializable) errorBeans));
		} else {
		    // change password & keep password history
		    String passwordHash = PasswordUtils.generateSecuredPasswordHash(newPassword);
		    BOUserPasswordHistory boUserPasswordHistory = new BOUserPasswordHistory();
		    boUserPasswordHistory.setUserId(boUser.getId());
		    boUserPasswordHistory.setLastPasswordHash(passwordHash);
		    boUserPasswordHistory.setLastPasswordDateTime(currentDateStr);
		    boUserPasswordHistoryDAO.save(boUserPasswordHistory);

		    boUser.setStatus(BOUser.STATUS_ACTIVE);
		    boUser.setPasswordHash(passwordHash);
		    boUser.setPasswordStatus(BOUser.PASSWORD_STATUS_COMPLETE);

		    // reset expire date
		    Calendar calendar = Calendar.getInstance(Locale.ENGLISH);
		    PasswordPolicyBean passwordPolicyBean = passwordPolicyService.getPasswordPolicy();
		    Integer passwordExpiryPeriodDay = 15;
		    if (passwordPolicyBean != null && passwordPolicyBean.getPasswordExpiryPeriodDay() != null) {
			passwordExpiryPeriodDay = passwordPolicyBean.getPasswordExpiryPeriodDay();
		    }
		    calendar.add(Calendar.DAY_OF_YEAR, passwordExpiryPeriodDay);
		    calendar.set(Calendar.HOUR_OF_DAY, 0);
		    calendar.set(Calendar.MINUTE, 0);
		    calendar.set(Calendar.SECOND, 0);
		    calendar.set(Calendar.MILLISECOND, 0);
		    boUser.setExpiredDate(StringUtils.formatDate(calendar.getTime(), StringUtils.PATTERN_DATE_PERSISTENCE));

		    boUser.setLastUpdateBy(boUserBean.getLoginUsername());
		    boUser.setLastUpdateDatetime(currentDateStr);
		    boUserPersistenceService.save(boUser);

		    // audit log
		    String logActionType = propertyService.getProperty("auditlog." + clazz + "." + method);
		    if (logActionType != null) {
			AuditLogBean auditLogBean = new AuditLogBean();
			auditLogBean.setLogActionType(logActionType);
			auditLogBean.setDescription(boUser.getUsername() + " change password by " + boUserBean.getLoginUsername());
			auditLogBean.setGroupId(boUserBean.getLoginRoleId());
			auditLogBean.setUserId(boUserBean.getLoginUserId());
			auditLogBean.setDateTime(currentDateStr);
			auditLogBean.setUserIp(boUserBean.getUserIp());
			auditLogService.createAuditLog(auditLogBean);
		    }
		    entryBeans.add(new EntryBean<String, Serializable>(boUserBean.getClass().getName(), boUserBean));
		}
	    }
	} catch (Exception e) {
	    ApplicationLogBean applicationLogBean = new ApplicationLogBean();
	    applicationLogBean.setType(ApplicationLogBean.TYPE_ERROR);
	    applicationLogBean.setClazz(clazz);
	    applicationLogBean.setMethod(method);
	    applicationLogBean.setMessage(e.getMessage());
	    applicationLogService.createApplicationLog(applicationLogBean);
	}
	return entryBeans;
    }

    private void createBOUserPasswordHistory(Long userId, String lastPasswordHash, String currentDateStr) {
	BOUserPasswordHistory boUserPasswordHistory = new BOUserPasswordHistory();
	boUserPasswordHistory.setUserId(userId);
	boUserPasswordHistory.setLastPasswordHash(lastPasswordHash);
	boUserPasswordHistory.setLastPasswordDateTime(currentDateStr);
	boUserPasswordHistoryDAO.save(boUserPasswordHistory);
    }

}
